<?php

/**
 * A document is a data structure which can be added to an index and
 * is returned from an index when searching. A document is essentially
 * a collection of ForageField objects.
 *
 * @author Rob Young <rob@roryoung.co.uk>
 * @package forage.document
 */
class ForageDocument implements ArrayAccess, Countable
{
  /**
   * array of ForageField objects
   */
  private $fields = array();

  /**
   * array of configuration properties
   */
  private $properties = array();

  /**
   * Magic accessor
   * Provides access to the documents fields as a single array.
   *
   * @param string $name
   * @return mixed
   */
  public function __get($name)
  {
    switch ($name) {
    case 'fields':
      $fields = array();
      foreach ($this->fields as $fieldSet) {
        $fields = array_merge($fields, array_values($fieldSet));
      }
      return $fields;
    case 'uid':
      foreach ($this->__get('fields') as $field) {
        if ($field->getProperty('uid')) {
          return $field;
        }
      }
      return null;
    case 'properties':
      return $this->properties;
    default:
      throw new InvalidArgumentException("Invalid property access [" . $name . "]");
    }
  }

  /**
   * Sets properties on the document.
   * Properties are settings on a document which are
   * not fields. For example, document level boosting.
   *
   * @param string $name
   * @param mixed $value
   */
  public function setProperty($name, $value)
  {
    $this->properties[$name] = $value;
  }
  
  /**
   * Get the value for a given property
   *
   * @param string $name
   * @return mixed
   */
  public function getProperty($name)
  {
    return $this->hasProperty($name) ? $this->properties[$name] : null;
  }

  /**
   * Check if a given property has been set
   *
   * @param string $name
   * @return mixed
   */
  public function hasProperty($name)
  {
    return isset($this->properties[$name]);
  }

  /**
   * Add a ForageField object to this ForageDocument
   *
   * @param ForageField $field
   */
  public function add($field, $value=null, $properties=array())
  {
    if ($field instanceof ForageField) {
      $this->addField($field);
    } elseif (is_string($field)) {
      $field = new ForageField($field, $value);
      foreach ($properties as $key => $value) {
        $field->setProperty($key, $value);
      }
      $this->addField($field);
    } else {
      throw new ForageException("Invalid arguments provided, expecting either a ForageField or two strings.");
    }
    return $this;
  }

  /**
   * Add a field to the document
   *
   * @param ForageField $field
   */
  protected function addField(ForageField $field)
  {
    if (!isset($this->fields[$field->name])) {
      $this->fields[$field->name] = array();
    }
    $this->fields[$field->name][] = $field;
    return $field;
  }

  /**
   * Remove a specific field from this document.
   *
   * @param ForageField $field
   * @return bool
   */
  public function remove(ForageField $field)
  {
    if (!$this->has($field)) return false;
    if (($i = array_search($field, $this->fields[$field->name], true)) === false) return false;
    array_splice($this->fields[$field->name], $i, 1);
    return true;
  }
  
  /**
   * Check if this document has a given field.
   *
   * @param ForageField $field
   * @return bool
   */
  public function has(ForageField $field)
  {
    if (isset($this->fields[$field->name])) {
      return in_array($field, $this->fields[$field->name], true);
    }
    return false;
  }

  /** ARRAY ACCESS METHODS **/

  /**
   * Check if we have any fields with a given name.
   * 
   * <example>
   * if (isset($document['fieldName'])) {
   *   // document has fields called 'fieldName'
   * }
   * </example>
   *
   * @param string $name
   * @return boolean
   */
  public function offsetExists($name)
  {
    return isset($this->fields[$name]);
  }

  /**
   * Get all fields with a given name.
   *
   * @param $name
   * @return array of ForageField objects
   */
  public function offsetGet($key)
  {
    if (!isset($this->fields[$key])) {
      return null;
    } elseif (count($this->fields[$key])>1) {
      return $this->fields[$key];
    } else {
      return $this->fields[$key][0];
    }
  }

  /**
   * Append a ForageField object to this ForageDocument.
   *
   * @param ForageField $field
   */
  public function offsetSet($key, $field)
  {
    if ($key!==null) {
      throw new InvalidArgumentException("ForageField objects can only be appended to the ForageDocument.");
    }
    if (!($field instanceof ForageField)) {
      throw new InvalidArgumentException("Expecting a ForageField object.");
    }
    $this->add($field);
  }

  /**
   * Remove all ForageField objects with a given name from
   * this ForageDocument.
   *
   * @param string $name
   */
  public function offsetUnset($name)
  {
    unset($this->fields[$name]);
  }

  /**
   * Count the total number of fields in the document.
   *
   * @return int
   */
  public function count()
  {
    return array_sum(array_map('count', $this->fields));
  }

  public function getFieldsByProperty($name, $value)
  {
    $fields = array();
    foreach ($this->fields as $field_set) {
      foreach ($field_set as $field) {
        if ($field->getProperty($name) == $value) {
          $fields[] = $field;
        }
      }
    }
    return $fields;
  }
}

